package simple.tree;

import struct.TreeNode;

import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

/**
 * <a href="https://leetcode-cn.com/problems/binary-tree-inorder-traversal/">94. 二叉树的中序遍历</a>
 * 给定一个二叉树的根节点 root ，返回它的 中序 遍历。
 * 示例 1：
 *   输入：root = [1,null,2,3]
 *   输出：[1,3,2]
 * 示例 2：
 *   输入：root = []
 *   输出：[]
 * 示例 3：
 *   输入：root = [1]
 *   输出：[1]
 * 示例 4：
 *   输入：root = [1,2]
 *   输出：[2,1]
 * 示例 5：
 *   输入：root = [1,null,2]
 *   输出：[1,2]
 * 提示：
 *   树中节点数目在范围 [0, 100] 内
 *   -100 <= Node.val <= 100
 * @author 刘学松
 * @date 2021-09-21 18:21
 */
public class 二叉树的中序遍历 {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        recursion(root, list);
        return list;
    }

    /**
     * 递归
     * @param node
     * @param list
     */
    public void recursion(TreeNode node, List<Integer> list) {
        if (node != null) {
            recursion(node.left, list);
            list.add(node.val);
            recursion(node.right, list);
        }
    }

    /**
     * 迭代
     * @param root
     * @param list
     */
    public List<Integer> traverse(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        Deque<TreeNode> stk = new LinkedList<TreeNode>();
        while (root != null || !stk.isEmpty()) {
            while (root != null) {
                stk.push(root);
                root = root.left;
            }
            root = stk.pop();
            res.add(root.val);
            root = root.right;
        }
        return res;
    }

    /**
     * Morris 中序遍历
     * Morris 遍历算法是另一种遍历二叉树的方法，它能将非递归的中序遍历空间复杂度降为 O(1)O(1)。
     * Morris 遍历算法整体步骤如下（假设当前遍历到的节点为 xx）：
     *   1. 如果 xx 无左孩子，先将 xx 的值加入答案数组，再访问 xx 的右孩子，即 x = x.x=x.right。
     *   2. 如果 xx 有左孩子，则找到 xx 左子树上最右的节点（即左子树中序遍历的最后一个节点，xx 在中序遍历中的前驱节点），我们记为 predecessor。根据 predecessor 的右孩子是否为空，进行如下操作。
     *     如果 predecessor 的右孩子为空，则将其右孩子指向 xx，然后访问 xx 的左孩子，即 x = x.x=x.left。
     *     如果 predecessor 的右孩子不为空，则此时其右孩子指向 xx，说明我们已经遍历完 xx 的左子树，我们将 predecessor 的右孩子置空，将 xx 的值加入答案数组，然后访问 xx 的右孩子，即 x = x.x=x.right。
     *   3. 重复上述操作，直至访问完整棵树。
     * @param root
     * @return
     */
    public List<Integer> morris(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        TreeNode predecessor = null;

        while (root != null) {
            if (root.left != null) {
                // predecessor 节点就是当前 root 节点向左走一步，然后一直向右走至无法走为止
                predecessor = root.left;
                while (predecessor.right != null && predecessor.right != root) {
                    predecessor = predecessor.right;
                }

                // 让 predecessor 的右指针指向 root，继续遍历左子树
                if (predecessor.right == null) {
                    predecessor.right = root;
                    root = root.left;
                }
                // 说明左子树已经访问完了，我们需要断开链接
                else {
                    res.add(root.val);
                    predecessor.right = null;
                    root = root.right;
                }
            }
            // 如果没有左孩子，则直接访问右孩子
            else {
                res.add(root.val);
                root = root.right;
            }
        }
        return res;
    }
}
